package cn.rayland.library.utils;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.math.BigInteger;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Semaphore;

import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils.Collections;

import cn.rayland.api.Gpx;
import cn.rayland.library.bean.FileTask;
import cn.rayland.library.bean.MachineState;
import cn.rayland.library.bean.MachineTask;
import cn.rayland.library.stm32.IIC;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.MessageQueue.IdleHandler;

public class X3gExecutors {
	// private static byte[] stop = new byte[]{1,0};
	private static byte[] pause = new byte[]{2, 0};
	private static byte[] reset = new byte[] { 8, 0 };
	private static byte[] reboot = new byte[] { 16, 0 };
	private static IIC stm32 = IIC.getInstance();
	private static Handler uiHandler = new Handler(Looper.getMainLooper());
	private static Handler taskHandler;
	private static Handler x3gHandler;
	private static volatile boolean isCancel;
	public static volatile MachineState state = new MachineState();
	private static FileTask stagTask;
	private static long lastNum;
	public static float[] position = new float[3];
	private static MachineManager manager;

	public static void init(MachineManager machineManager) {
		if (stm32 == null) {
			System.err.println("stm32 not found");
			return;
		}
		if (x3gHandler == null && taskHandler == null) {
			manager = machineManager;
			HandlerThread thread = new HandlerThread("sendX3g");
			HandlerThread taskThread = new HandlerThread("sendTask");
			taskThread.start();
			thread.start();
			x3gHandler = new Handler(thread.getLooper());
			taskHandler = new Handler(taskThread.getLooper());
			x3gHandler.getLooper().myQueue().addIdleHandler(new IdleHandler() {
				public boolean queueIdle() {
					readData();
					return true;
				}
			});
		}
	}

	private static void readData() {
		// read from stm32
		byte[] data = stm32.readData(7);
		if (data != null) {
			if ((data[3] & (1 << 1)) != 0) {
				int a = data[6] & 0xff;
				int b = data[5] & 0xff;
				final float distance = (a * 256 + b + 10000) / 1000f;
				uiHandler.post(new Runnable() {
					public void run() {
						if (state.getTask() != null
								&& state.getTask().getGcodeCallback() != null) {
							state.getTask().getGcodeCallback().onM900(distance);
						}
					}
				});
				byte[] byte2 = new byte[2];
				byte2[0] = 0;
				byte2[1] = 2;
				stm32.sendData(byte2, 0, byte2.length);
			} else {
				state.setLastlinenum(FormatUtils.byte2short(new byte[] {
						data[5], data[6] }) & 0x0FFFF);
			}
			state.setVal(FormatUtils
					.byte2short(new byte[] { data[0], data[1] }) & 0x0FFFF);
			state.setTemper((new BigInteger(1, new byte[] { data[2] })
					.intValue()) * 2);
			state.setWorking((data[4] & 1) != 0);
			state.setMoving((data[4] & (1 << 1)) != 0);
			state.setExtruding((data[4] & (1 << 2)) != 0);
			state.setHeating(new boolean[] { (data[4] & (1 << 3)) != 0,
					(data[4] & (1 << 4)) != 0 });
			state.setFanning(new boolean[] { (data[4] & (1 << 5)) != 0,
					(data[4] & (1 << 6)) != 0 });
			state.setError((data[4] & (1 << 7)) != 0);
			if (state.isError()) {
				System.err.println("stm32 error");
				uiHandler.post(new Runnable() {
					public void run() {
						if (state.getTask() != null
								&& state.getTask().getTaskCallback() != null) {
							state.getTask().getTaskCallback().onError();
						}
						state.setTask(null);
						isCancel = true;
					}
				});
			}
		}
	}

	public static void execInitBytes() {
		if (taskHandler == null) {
			System.err.println("X3gExecutors not init");
			return;
		}
		byte[] initX3g = Gpx.getInitX3g();
		ByteArrayInputStream bis = new ByteArrayInputStream(initX3g);
		execStream(null, bis);
	}

	public static void execStream(final MachineTask<?> task,
			final InputStream is) {
		if (taskHandler == null) {
			System.err.println("X3gExecutors not init");
			return;
		}
		taskHandler.post(new Runnable() {
			public void run() {
				try {
					state.setTask(task);
					if (task != null && task.getTaskCallback() != null) {
						task.getTaskCallback().onStart();
					}
					isCancel = false;
					byte[] bytes = new byte[256];
					int length = -1;
					int index = 0;
					int position = 0;
					while (!isCancel) {
						if ((length = X3gParser.readCommand(is, bytes)) != -1) {
							position += length;
							if(state.getTask()!=null){
								state.getTask().addIndex(index, position);
							}
							index ++;
							execBlockedBytes(bytes, 0, length);
							
						} else {
							if (!state.isWorking()) {
								isCancel = true;
								uiHandler.post(new Runnable() {
									public void run() {
										if (state.getTask() != null
												&& state.getTask()
														.getTaskCallback() != null) {
											state.getTask().getTaskCallback()
													.onFinished();
										}
										state.setTask(null);
									}
								});
							}
						}

					}
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					try {
						is.close();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
		});
	}

	public static void execBlockedBytes(final byte[] bytes, final int offset,
			final int length) {
		if (x3gHandler == null) {
			System.err.println("X3gExecutors not init");
			return;
		}
		final Semaphore semaphore = new Semaphore(0);
		x3gHandler.post(new Runnable() {
			public void run() {
				byte[] datas = new byte[length + 2];
				datas[0] = datas[1] = 0;
				System.arraycopy(bytes, offset, datas, 2, length);
				while (!isCancel && !state.isError()) {
					readData();
					if (state.getVal() < length) {
						continue;
					}
					if(!isCancel && !state.isError()){
						stm32.sendData(datas);
						printHexString(datas);
						if (state.getTask() != null) {
							state.getTask().setSend(
									state.getTask().getSend() + length);
						}
					}
					break;
				}
				semaphore.release();
			}
		});
		try {
			semaphore.acquire();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public static void execInsertBytes(final byte[] bytes) {
		if (x3gHandler == null) {
			System.err.println("X3gExecutors not init");
			return;
		}
		x3gHandler.postAtFrontOfQueue(new Runnable() {
			public void run() {
				ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
				byte[] bytes = new byte[256];
				int length = -1;
				while ((length = X3gParser.readCommand(bis, bytes)) != -1 && !state.isError()) {
					byte[] datas = new byte[length + 2];
					datas[0] = datas[1] = 0;
					System.arraycopy(bytes, 0, datas, 2, length);

					while (!state.isError()) {
						readData();
						if (state.getVal() < length) {
							continue;
						}
						System.out.println("execute insert bytes:");
						printHexString(datas);
						stm32.sendData(datas);
						break;
					}
				}
				try {
					bis.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		});
	}

	public static void execReset() {
		if (stm32 != null) {
			isCancel = true;
			stm32.sendData(reset);
			System.out.println("stm32 reset");
			uiHandler.post(new Runnable() {
				public void run() {
					if (state.getTask() != null
							&& state.getTask().getTaskCallback() != null) {
						state.getTask().getTaskCallback().onCancel();
					}
					state.setTask(null);
				}
			});
		}
	}

	/**
	 * pause print, remember lastlinenum and position, move extruder away
	 */
	public static void execPause(){	
		if(stm32 != null){
			isCancel = true;
			taskHandler.post(new Runnable() {
				public void run() {
					stagTask = (FileTask) state.getTask();
					lastNum = state.getLastlinenum();
					stm32.sendData(pause);
					System.out.println("stm32 pause");
					RandomAccessFile acf = null;
					try {
						acf = new RandomAccessFile(stagTask.getContent(), "rw");
						int size = stagTask.getIndexs().size();
						Long[] values = new Long[size];
						stagTask.getIndexs().values().toArray(values);
						for(int i=size;i>0;i--){
							acf.seek(values[i-1]);
							int command = acf.read();
							if(command == 155){
								byte[] bytes = new byte[4];
								acf.read(bytes);
								position[0] = (float) (FormatUtils.byte2int(bytes) / manager.machine.axis_x.steps_per_mm);
								acf.read(bytes);
								position[1] = (float) (FormatUtils.byte2int(bytes) / manager.machine.axis_y.steps_per_mm);
								acf.read(bytes);
								position[2] = (float) (FormatUtils.byte2int(bytes) / manager.machine.axis_z.steps_per_mm);
								System.out.println("current position: "+position[0]+" "+position[1]+" "+position[2]);
								manager.sendCommand("G1 X"+(position[0]-20)+" Y"+(position[1]-20)+" Z"+(position[2]+10));
								break;
							}
						}
					} catch (Exception e) {
						e.printStackTrace();
					}finally{
						if(acf!=null){
							try {
								acf.close();
							} catch (IOException e) {
								e.printStackTrace();
							}
						}
					}
				}
			});
		}
	}
	
	public static void execResume(){	
		if(isCancel && stagTask!=null){
			taskHandler.post(new Runnable() {
				public void run() {
					FileInputStream is = null;
					try {
						state.setTask(stagTask);
						isCancel = false;
						is = new FileInputStream(stagTask.getContent());
						long skip = stagTask.getIndexs().get(lastNum);
						is.skip(skip);
						byte[] bytes = new byte[256];
						int length = -1;
						long index = lastNum;
						long position = stagTask.getIndexs().get(index);
						while (!isCancel) {
							if ((length = X3gParser.readCommand(is, bytes)) != -1) {
								position += length;
								index ++;
								if(state.getTask()!=null){
									state.getTask().addIndex(index, position);
								}
								execBlockedBytes(bytes, 0, length);
								
							} else {
								if (!state.isWorking()) {
									isCancel = true;
									uiHandler.post(new Runnable() {
										public void run() {
											if (state.getTask() != null
													&& state.getTask()
															.getTaskCallback() != null) {
												state.getTask().getTaskCallback()
														.onFinished();
											}
											state.setTask(null);
										}
									});
								}
							}

						}
					} catch (Exception e) {
						e.printStackTrace();
					} finally {
						try {
							is.close();
						} catch (IOException e) {
							e.printStackTrace();
						}
					}
				}
			});
		}
	}
	
	public static void execReboot() {
		if (stm32 != null) {
			stm32.sendData(reboot);
			System.out.println("stm32 reboot");
		}
	}

	public static void printHexString(byte[] b) {
		for (int i = 0; i < b.length; i++) {
			String hex = Integer.toHexString(b[i] & 0xFF);
			if (hex.length() == 1) {
				hex = '0' + hex;
			}
			System.out.print(hex.toUpperCase() + " ");
		}
		System.out.println();
	}

	public static void destory() {
		if (x3gHandler != null) {
			x3gHandler.getLooper().quitSafely();
			x3gHandler = null;
		}
		if (taskHandler != null) {
			taskHandler.getLooper().quitSafely();
			taskHandler = null;
		}
	}
}
